// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <stddef.h>
#include <stdint.h>

#include <map>

#include "base/compiler_specific.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "content/renderer/pepper/pepper_device_enumeration_host_helper.h"
#include "ppapi/c/pp_errors.h"
#include "ppapi/host/host_message_context.h"
#include "ppapi/host/ppapi_host.h"
#include "ppapi/host/resource_host.h"
#include "ppapi/proxy/ppapi_message_utils.h"
#include "ppapi/proxy/ppapi_messages.h"
#include "ppapi/proxy/resource_message_params.h"
#include "ppapi/proxy/resource_message_test_sink.h"
#include "ppapi/shared_impl/ppapi_permissions.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

namespace content {

namespace {

    std::vector<ppapi::DeviceRefData> TestEnumerationData()
    {
        std::vector<ppapi::DeviceRefData> data;
        ppapi::DeviceRefData data_item;
        data_item.type = PP_DEVICETYPE_DEV_AUDIOCAPTURE;
        data_item.name = "name_1";
        data_item.id = "id_1";
        data.push_back(data_item);
        data_item.type = PP_DEVICETYPE_DEV_VIDEOCAPTURE;
        data_item.name = "name_2";
        data_item.id = "id_2";
        data.push_back(data_item);

        return data;
    }

    class TestDelegate : public PepperDeviceEnumerationHostHelper::Delegate,
                         public base::SupportsWeakPtr<TestDelegate> {
    public:
        TestDelegate()
            : last_used_id_(0)
        {
        }

        ~TestDelegate() override { CHECK(monitoring_callbacks_.empty()); }

        void EnumerateDevices(PP_DeviceType_Dev /* type */,
            const GURL& /* document_url */,
            const DevicesCallback& callback) override
        {
            callback.Run(TestEnumerationData());
        }

        uint32_t StartMonitoringDevices(PP_DeviceType_Dev /* type */,
            const GURL& /* document_url */,
            const DevicesCallback& callback) override
        {
            last_used_id_++;
            monitoring_callbacks_[last_used_id_] = callback;
            return last_used_id_;
        }

        void StopMonitoringDevices(PP_DeviceType_Dev /* type */,
            uint32_t subscription_id) override
        {
            auto iter = monitoring_callbacks_.find(subscription_id);
            CHECK(iter != monitoring_callbacks_.end());
            monitoring_callbacks_.erase(iter);
        }

        // Returns false if |request_id| is not found.
        bool SimulateDevicesChanged(
            uint32_t subscription_id,
            const std::vector<ppapi::DeviceRefData>& devices)
        {
            auto iter = monitoring_callbacks_.find(subscription_id);
            if (iter == monitoring_callbacks_.end())
                return false;

            iter->second.Run(devices);
            return true;
        }

        size_t GetRegisteredCallbackCount() const
        {
            return monitoring_callbacks_.size();
        }

        int last_used_id() const { return last_used_id_; }

    private:
        std::map<uint32_t, DevicesCallback> monitoring_callbacks_;
        int last_used_id_;

        DISALLOW_COPY_AND_ASSIGN(TestDelegate);
    };

    class PepperDeviceEnumerationHostHelperTest : public testing::Test {
    protected:
        PepperDeviceEnumerationHostHelperTest()
            : ppapi_host_(&sink_, ppapi::PpapiPermissions())
            , resource_host_(&ppapi_host_, 12345, 67890)
            , device_enumeration_(&resource_host_,
                  delegate_.AsWeakPtr(),
                  PP_DEVICETYPE_DEV_AUDIOCAPTURE,
                  GURL("http://example.com"))
        {
        }

        ~PepperDeviceEnumerationHostHelperTest() override { }

        void SimulateMonitorDeviceChangeReceived(uint32_t callback_id)
        {
            PpapiHostMsg_DeviceEnumeration_MonitorDeviceChange msg(callback_id);
            ppapi::proxy::ResourceMessageCallParams call_params(
                resource_host_.pp_resource(), 123);
            ppapi::host::HostMessageContext context(call_params);
            int32_t result = PP_ERROR_FAILED;
            ASSERT_TRUE(
                device_enumeration_.HandleResourceMessage(msg, &context, &result));
            EXPECT_EQ(PP_OK, result);
        }

        void CheckNotifyDeviceChangeMessage(
            uint32_t callback_id,
            const std::vector<ppapi::DeviceRefData>& expected)
        {
            ppapi::proxy::ResourceMessageReplyParams reply_params;
            IPC::Message reply_msg;
            ASSERT_TRUE(sink_.GetFirstResourceReplyMatching(
                PpapiPluginMsg_DeviceEnumeration_NotifyDeviceChange::ID,
                &reply_params,
                &reply_msg));
            sink_.ClearMessages();

            EXPECT_EQ(PP_OK, reply_params.result());

            uint32_t reply_callback_id = 0;
            std::vector<ppapi::DeviceRefData> reply_data;
            ASSERT_TRUE(ppapi::UnpackMessage<
                PpapiPluginMsg_DeviceEnumeration_NotifyDeviceChange>(
                reply_msg, &reply_callback_id, &reply_data));
            EXPECT_EQ(callback_id, reply_callback_id);
            EXPECT_EQ(expected, reply_data);
        }

        TestDelegate delegate_;
        ppapi::proxy::ResourceMessageTestSink sink_;
        ppapi::host::PpapiHost ppapi_host_;
        ppapi::host::ResourceHost resource_host_;
        PepperDeviceEnumerationHostHelper device_enumeration_;
        base::MessageLoop message_loop_; // required for async calls to work.

    private:
        DISALLOW_COPY_AND_ASSIGN(PepperDeviceEnumerationHostHelperTest);
    };

} // namespace

TEST_F(PepperDeviceEnumerationHostHelperTest, EnumerateDevices)
{
    PpapiHostMsg_DeviceEnumeration_EnumerateDevices msg;
    ppapi::proxy::ResourceMessageCallParams call_params(
        resource_host_.pp_resource(), 123);
    ppapi::host::HostMessageContext context(call_params);
    int32_t result = PP_ERROR_FAILED;
    ASSERT_TRUE(
        device_enumeration_.HandleResourceMessage(msg, &context, &result));
    EXPECT_EQ(PP_OK_COMPLETIONPENDING, result);
    base::RunLoop().RunUntilIdle();

    // A reply message should have been sent to the test sink.
    ppapi::proxy::ResourceMessageReplyParams reply_params;
    IPC::Message reply_msg;
    ASSERT_TRUE(sink_.GetFirstResourceReplyMatching(
        PpapiPluginMsg_DeviceEnumeration_EnumerateDevicesReply::ID,
        &reply_params,
        &reply_msg));

    EXPECT_EQ(call_params.sequence(), reply_params.sequence());
    EXPECT_EQ(PP_OK, reply_params.result());

    std::vector<ppapi::DeviceRefData> reply_data;
    ASSERT_TRUE(ppapi::UnpackMessage<
        PpapiPluginMsg_DeviceEnumeration_EnumerateDevicesReply>(reply_msg,
        &reply_data));
    EXPECT_EQ(TestEnumerationData(), reply_data);
}

TEST_F(PepperDeviceEnumerationHostHelperTest, MonitorDeviceChange)
{
    uint32_t callback_id = 456;
    SimulateMonitorDeviceChangeReceived(callback_id);

    EXPECT_EQ(1U, delegate_.GetRegisteredCallbackCount());
    int request_id = delegate_.last_used_id();

    std::vector<ppapi::DeviceRefData> data;
    ASSERT_TRUE(delegate_.SimulateDevicesChanged(request_id, data));

    // StopEnumerateDevices() shouldn't be called because the MonitorDeviceChange
    // message is a persistent request.
    EXPECT_EQ(1U, delegate_.GetRegisteredCallbackCount());

    CheckNotifyDeviceChangeMessage(callback_id, data);

    ppapi::DeviceRefData data_item;
    data_item.type = PP_DEVICETYPE_DEV_AUDIOCAPTURE;
    data_item.name = "name_1";
    data_item.id = "id_1";
    data.push_back(data_item);
    data_item.type = PP_DEVICETYPE_DEV_VIDEOCAPTURE;
    data_item.name = "name_2";
    data_item.id = "id_2";
    data.push_back(data_item);
    ASSERT_TRUE(delegate_.SimulateDevicesChanged(request_id, data));
    EXPECT_EQ(1U, delegate_.GetRegisteredCallbackCount());

    CheckNotifyDeviceChangeMessage(callback_id, data);

    uint32_t callback_id2 = 789;
    SimulateMonitorDeviceChangeReceived(callback_id2);

    // StopEnumerateDevice() should have been called for the previous request.
    EXPECT_EQ(1U, delegate_.GetRegisteredCallbackCount());
    int request_id2 = delegate_.last_used_id();

    data_item.type = PP_DEVICETYPE_DEV_AUDIOCAPTURE;
    data_item.name = "name_3";
    data_item.id = "id_3";
    data.push_back(data_item);
    ASSERT_TRUE(delegate_.SimulateDevicesChanged(request_id2, data));

    CheckNotifyDeviceChangeMessage(callback_id2, data);

    PpapiHostMsg_DeviceEnumeration_StopMonitoringDeviceChange msg;
    ppapi::proxy::ResourceMessageCallParams call_params(
        resource_host_.pp_resource(), 123);
    ppapi::host::HostMessageContext context(call_params);
    int32_t result = PP_ERROR_FAILED;
    ASSERT_TRUE(
        device_enumeration_.HandleResourceMessage(msg, &context, &result));
    EXPECT_EQ(PP_OK, result);

    EXPECT_EQ(0U, delegate_.GetRegisteredCallbackCount());
}

} // namespace content
